﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Timers;

using ecdl.demo.model.question;
using ecdl.demo.model.util;

using Ninject;

namespace ecdl.demo.model {

    public class TestRun : INotifyPropertyChanged {
        private Test test;

        private readonly IDictionary<ITask, ITaskEvaluation> submittedAnsweres = new Dictionary<ITask, ITaskEvaluation>();

        private ITestPresenter testPresenter;
        private ITaskPresenter taskPresenter;
        private ITestTimer timer;
        private IVolumeProvider pathProvider;
        private ITaskEvaluationPresenter taskEvaluationPresenter;

        private IEnumerator<ITask> taskEnumerator;

        private bool started = false;

        [Inject]
        public TestRun(Test test, ITestPresenter testPresenter, ITaskPresenter taskPresenter, ITestTimer timer, IVolumeProvider volumeProvider, ITaskEvaluationPresenter taskEvaluationPresenter) {
            this.test = test;
            this.testPresenter = testPresenter;
            this.taskPresenter = taskPresenter;
            this.timer = timer;
            this.pathProvider = volumeProvider;
            this.taskEvaluationPresenter = taskEvaluationPresenter;

            //create an circular enumerator that will only return unanswered tasks
            taskEnumerator = CreateCircularEnumerator(test);
            //initilaize the enumerator onto the first task
            taskEnumerator.MoveNext();

            RegisterEventHandlers(testPresenter, timer);
            //initially present the current task
            UpdatePresenters();
        }

        private IEnumerator<ITask> CreateCircularEnumerator(Test test) {
            IEnumerator<ITask> tasks = test.Tasks.GetEnumerator();
            PredicateEnumerator<ITask> unansweredTasks = new PredicateEnumerator<ITask>(tasks, task => !IsAnswered(task));
            return new CircularEnumerator<ITask>(unansweredTasks);
        }

        private void RegisterEventHandlers(ITestPresenter testPresenter, ITestTimer timer) {
            testPresenter.SkipClicked += new EventHandler(SkipCurrentTask);
            testPresenter.SubmitClicked += new EventHandler(SubmitCurrentTask);
            testPresenter.EndClicked += new EventHandler(EndTest);
            timer.TimeUpdateEvent += new EventHandler<GenericEventArgs<TimeSpan>>(UpdateTime);
        }

        /// <summary>
        /// tells the TestPresenter to update its time-label to the given value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void UpdateTime(object sender, GenericEventArgs<TimeSpan> e) {
            testPresenter.TimeLeft = e.Value;
        }

        void EndTest(object sender, EventArgs e) {

            testPresenter.Close();
            taskPresenter.Close();
            pathProvider.Close();
            started = false;
        }

        void SubmitCurrentTask(object sender, EventArgs e) {
            if (CurrentTask.CanSubmit) {
                SubmitAnswer(CurrentTask);
                Forward();
                UpdatePresenters();
            } else {
                //show tooltip
            }
        }

        void UpdatePresenters() {
            if (CurrentTask != null) {
                //update the progress label
                testPresenter.SetProgress(CurrentTaskNumber, test.NumberOfTasks);
                // update the task-description
                testPresenter.TaskDescription = CurrentTask.Text;
                CurrentTask.Activate();

            } else {
                if (started)
                    EndTest(this, EventArgs.Empty);
            }
        }

        void SkipCurrentTask(object sender, EventArgs e) {
            Forward();
            UpdatePresenters();
        }

        public int NumberOfTasks {
            get {
                return test.NumberOfTasks;
            }
        }

        public ITask CurrentTask {
            get { return taskEnumerator.Current; }
        }

        private int CurrentTaskIndex {
            get {
                return test.Tasks.IndexOf(CurrentTask);
            }
        }

        public int CurrentTaskNumber {
            get { return CurrentTaskIndex + 1; }
        }

        public bool CanForward {
            get {
                int maxIndex = test.Tasks.Count - 1;
                return (CurrentTaskIndex < maxIndex);
            }
        }

        public bool CanBackward {
            get {
                return (CurrentTaskIndex > 0);
            }
        }

        public void Forward() {
            taskEnumerator.MoveNext();
        }

        /// <summary>
        /// Submits a set of answeres for the given question. The question must be part of the Questions 
        /// property and the answere must be part of the question's possible answeres.
        /// </summary>
        /// <param name="question"></param>
        /// <param name="answeres"></param>
        public void SubmitAnswer(ITask task) {
            timer.Pause();
            ITaskEvaluation evaluation = task.Evaluate();
            taskPresenter.ShowEvaluation(evaluation);
            submittedAnsweres[task] = evaluation;
            taskEvaluationPresenter.ShowTaskEvaluation(evaluation.IsCorrect, evaluation.Cause);

            timer.Resume();
        }

        /// <summary>
        /// Returns the answers that where previously submitted for the given question
        /// </summary>
        public ITaskEvaluation GetSubmittedAnswerFor(ITask task) {
            ITaskEvaluation evalution = null;
            submittedAnsweres.TryGetValue(task, out evalution);
            return evalution;
        }

        public bool IsAnswered(ITask task) {
            ITaskEvaluation evalution = GetSubmittedAnswerFor(task);
            return evalution != null;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyname) {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) {
                PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyname);
                handler(this, e);
            }
        }

        public void Start() {
            started = true;
            InitializeTasksPreconditions();
            testPresenter.Show();
            timer.Start();
        }

        private void InitializeTasksPreconditions() {
            foreach (var task in test.Tasks) {
                task.InitializePreconditions();
            }
        }

        void timer_Elapsed(object sender, ElapsedEventArgs e) {

        }
    }
}
